﻿using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

namespace SharePointSearchBench.Library
{
    /// <summary>
    /// Represents a constraint used in a full text search WHERE clause.
    /// </summary>
    public class Condition
    {
        string conditionText;
        Condition logicalPeer;
        LogicalOperator peerOperator;

        private Condition(string conditionText)
        {
            this.conditionText = conditionText;
        }

        /// <summary>
        /// Creates a new Condition object with a single FREETEXT predicate.
        /// </summary>
        /// <param name="freeTextPhrase"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c = Condition.FreeText("dog cat");
        /// Console.WriteLine(c.ToString());
        /// //the above statement will write the following:
        /// //FREETEXT(DEFAULTPROPERTIES, 'dog cat')
        /// </code></example>
        public static Condition FreeText(string freeTextPhrase)
        {
            string conditionText = string.Format("FREETEXT({0}, '{1}')", "DEFAULTPROPERTIES", freeTextPhrase);
            return new Condition(conditionText);
        }

        /// <summary>
        /// Creates a new Condition object with a single CONTAINS predicate.
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c = Condition.Contains("Path", "chaska");
        /// Console.WriteLine(c.ToString());
        /// //the above statement will write the following:
        /// //CONTAINS(Path, 'chaska')
        /// </code></example>
        public static Condition Contains(string fieldName, string value)
        {
            string conditionText = string.Format("CONTAINS({0}, '{1}')", fieldName, value);
            return new Condition(conditionText);
        }

        /// <summary>
        /// Creates a new Condition object with a single scope constraint.
        /// </summary>
        /// <param name="scopeName"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c = Condition.FromScope("Accounting");
        /// Console.WriteLine(c.ToString());
        /// //the above statement will write the following:
        /// //"Scope" = 'Accounting'
        /// </code></example>
        public static Condition FromScope(string scopeName)
        {
            string conditionText = string.Format("\"Scope\" = '{0}'", scopeName);
            return new Condition(conditionText);
        }

        /// <summary>
        /// Creates a new Condition object with a single field/value equality constraint.
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c = Condition.StringComparison("Author", "John Doe");
        /// Console.WriteLine(c.ToString());
        /// //the above statement will write the following:
        /// //Author = 'John Doe'
        /// </code></example>
        public static Condition StringComparison(string fieldName, string value)
        {
            string conditionText = string.Format("{0} = '{1}'", fieldName, value);
            return new Condition(conditionText);
        }

        /// <summary>
        /// Creates a new Condition object with a single field/value equality constraint.
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c = Condition.NumericComparison("Quantity", 7);
        /// Console.WriteLine(c.ToString());
        /// //the above statement will write the following:
        /// //Quantity = 7
        /// </code></example>
        public static Condition NumericComparison(string fieldName, int value)
        {
            string conditionText = string.Format("{0} = {1}", fieldName, value.ToString());
            return new Condition(conditionText);
        }

        /// <summary>
        /// Joins the condition with another.  Both conditions must be met for an item to be included in results.
        /// </summary>
        /// <param name="peer"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c1 = Condition.NumericComparison("Quantity", 7);
        /// Condition c2 = Condition.Contains("Path", "chaska");
        /// c2 = c2.And(c1);
        /// Console.WriteLine(c2.ToString());
        /// //the above statement will write the following:
        /// //((CONTAINS("Path", 'chaska')) AND (Quantity = 7))
        /// </code></example>
        public Condition And(Condition peer)
        {
            this.logicalPeer = peer;
            this.peerOperator = LogicalOperator.And;
            return this;
        }

        /// <summary>
        /// Joins the condition with another.  One of the conditions must be met for an item to be included in the results.
        /// </summary>
        /// <param name="peer"></param>
        /// <returns></returns>
        /// <example><code>
        /// Condition c1 = Condition.NumericComparison("Quantity", 7);
        /// Condition c2 = Condition.Contains("Path", "chaska");
        /// c2 = c2.Or(c1);
        /// Console.WriteLine(c2.ToString());
        /// //the above statement will write the following:
        /// //((CONTAINS("Path", 'chaska')) OR (Quantity = 7))
        /// </code></example>
        public Condition Or(Condition peer)
        {
            this.logicalPeer = peer;
            this.peerOperator = LogicalOperator.Or;
            return this;
        }

        /// <summary>
        /// Writes out the condition as it will appear within a WHERE clause.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if(this.logicalPeer == null)
            {
                return this.GetConditionValue();
            }
            else
            {
                string logicalOperator = "AND";
                
                if(this.peerOperator == LogicalOperator.Or)
                {
                    logicalOperator = "OR";
                }
                
                string final = string.Format("({0} {2} {1})", 
                    this.GetConditionValue(), 
                    this.logicalPeer.ToString(), 
                    logicalOperator);

                return final;
            }
        }

        string GetConditionValue()
        {
            return string.Format("({0})", this.conditionText);
        }
    }
}
